/*
*	CLASE CONTENEDOR
*
*
*/
#ifndef _CONTAINER_H_
#define _CONTAINER_H_

#include "constantes.h"
#include "Box.h"



//Numero de bloques en los que se dividirá el contenedor para repartir la carga
#define NUM_BLOQUES 2

extern double W_LIMIT;
extern int WIDTH;
extern int HEIGHT;
extern int DEPTH;

using namespace std;

typedef struct {
	double weight;
	int ini, fin;
	int n_cajas;
}bloque;

class corner{
public:
	corner();
	corner(double , double , double );
	corner( const corner & c);
	corner& operator=(const corner &);
	int x;
    int y;
    int z;
    double w;
    double h;
    double d;
    Box * base;     //Peso máximo que se puede colocar en la esquina
};

corner::corner(){
	this->x=0;
	this->y=0;
	this->z=0;
	this->w=WIDTH;
	this->h=HEIGHT;
	this->d=DEPTH;
	this->base=NULL;
}

corner::corner(double W, double H, double D){
	this->x=0;
	this->y=0;
	this->z=0;
	this->w=W;
	this->h=H;
	this->d=D;
	this->base=NULL;
}
corner::corner( const corner & c)
{
	this->operator=(c);
}

corner& corner::operator=(const corner &c)
{
	this->x=c.x;
	this->y=c.y;
	this->z=c.z;
	this->w=c.w;
	this->h=c.h;
	this->d=c.d;
	this->base=c.base;

	return *this;

}

class Container{
public:
	Container(){}
	Container(double w, double h, double d, double hl, double wl);
	Container( const Container & c);
	//~Container();
	bool empty();

	Container& operator=(const Container &);
	void asignaCajas(vector<pair<int,Box> >& , int);
	void asignaPesoBloque(Box &caja);

	void Reset();

	double getWidth(){return width;}
	double getHeight(){return height;}
	double getDepth(){return depth;}
	double getWeight(){return weight;}
	double getHeightLim(){return height_lim;}
	double getWeightLim(){return weight_lim;}
	int getID(){return id;}
	void setID(int ID){id = ID;}
	
	double getOcupacion(){return ocupacion;}
	void setOcupacion(double oc){ocupacion=oc;}

	double getArea(){return area;}
	void setArea(double a){area=a;}

	LONGD getVolume(){return volume;}
	void setVolume(LONGD v) { volume=v; }

	list<Box>* getBoxes(){return &colocadas;}
	void pushBackBox(Box &b);
	void insertBox(Box &b, list<Box>::iterator &);
	bool allocateBox(Box &b);

	int getPlacedBoxes(){return ultima_caja+1;}

	double getFitness(){return fitness_solucion;}
	void setFitness(double f){fitness_solucion = f;}

	string getCliente(){return cliente;}
	void setCliente(string c){cliente=c;}

	double getPesoBloqueZ(int b){return bloques_z[b].weight;}
	double getPesoBloqueX(int b){return bloques_x[b].weight;}

	bool selectBaseCorner(Box &, corner &);
	void genBaseCorners(Box &);
	
	int id;
	string cliente;
	int ultima_caja; //Índice a la última caja colocada
	list<Box> colocadas;
	list<Box> cajasBase;
	list<corner> esquinas;

	vector<bloque> bloques_z;	//Para el balanceo de pesos
	vector<bloque> bloques_x;	//Z es largo; X es ancho
	
	int oz, ox, oy;

	double ocupacion,	//% de ocupación
		   z_max,
		   area;		//% de la base ocupado

private:
	double width, height, depth, weight, height_lim, weight_lim;
	double fitness_solucion;

	LONGD volume;	//volumen total del contenedor

};

Container::Container(double w, double h, double d, double hl, double wl){
	this->width=w;
	this->height=h;
	this->depth=d;
	this->weight=0;
	this->height_lim=hl;
	this->weight_lim=wl;
	this->volume = (LONGD)w*(LONGD)h*(LONGD)d;	//El contenedor está vacío
	this->colocadas= list<Box>();
	this->cajasBase= list<Box>();
	this->bloques_z=vector<bloque>(NUM_BLOQUES);
	this->bloques_x=vector<bloque>(NUM_BLOQUES);
	this->ultima_caja=-1;	
	this->z_max=0;
    this->area=0;
	this->ocupacion=0;

	//Creo los bloques para el contenedor actual
	int z_aux, x_aux;
	z_aux=x_aux=0;
	for(int i=0; i<NUM_BLOQUES; i++)
	{	
		bloques_z[i].weight=0;
		bloques_z[i].ini = z_aux;
		bloques_z[i].fin = z_aux + depth/(double)NUM_BLOQUES;
		z_aux = bloques_z[i].fin;

		bloques_x[i].weight=0;
		bloques_x[i].ini = x_aux;
		bloques_x[i].fin = x_aux + width/(double)NUM_BLOQUES;
		x_aux = bloques_x[i].fin;
	}
	this->esquinas.push_back(corner());

}

Container::Container( const Container & c)
{
	this->operator=(c);
}

/*Devuelve true si el contenedor esta vacio*/
bool Container::empty(){
	return (this->ultima_caja == -1);
}

void Container::Reset()
{
	this->ultima_caja=-1;
	this->z_max=0;
    this->area=0;
	this->ocupacion=0;
	this->weight=0;
	this->bloques_z[0].weight=0;
	this->bloques_z[1].weight=0;
	this->bloques_x[0].weight=0;
	this->bloques_x[1].weight=0;
	this->colocadas.clear();
	this->cajasBase.clear();

	this->esquinas.clear();
	this->esquinas.push_back(corner());
}

void Container::pushBackBox(Box &b)
{
	list<Box>::iterator it=colocadas.end();
	this->insertBox(b,it);
}

//Inserta la caja en el contenedor tras la caja apuntada por it
void Container::insertBox(Box &b, list<Box>::iterator &it)
{
	if(ultima_caja <= (int)colocadas.size()-1)
	{
		ultima_caja++;	

		if(it==colocadas.end())
			colocadas.push_back(b);
		else
		{
			++it;
			colocadas.insert(it,b);
		}

		ocupacion += (b.getVolume()/this->volume)*100.0;
		if(b.getY()==0)
		{
			area += (b.getWidth()*b.getDepth()/(double)(this->width*this->depth))*100.0;
			cajasBase.push_back(b);
		}
		asignaPesoBloque(b);
		weight+=b.getWeight();

		//Si al colocar una caja supero el largo que tenia hasta ahora, actualizo z_max del contenedor
		if(this->z_max < b.getZ()+b.getDepth())
			this->z_max = b.getZ()+b.getDepth();
	}
}


/*
*	Introduce en el contenedor la caja de acuerdo a la especificación del fitness 6.
*
*	Retorna TRUE si la caja se introdujo en el contendor.
*	Retorna FALSE en caso contrario
*/
void cleanCorners(Container&, const Box &);
bool delCorner(Box &, list<corner> &);
bool Container::allocateBox(Box &b)
{
	//Comprobación preliminar: peso y altura
	if(this->weight + b.getWeight() > this->weight_lim
		|| b.getHeight() > height)
		return false;

	list<Box>::reverse_iterator it_col;
	list<Box>::iterator it_aux,
			    it_col_base;
	int i,
		cont,
		z_base, x_base, y_base;
	bool salir;
	corner seleccionada;
	list<Box> colocadas_aux;

	//############		COLOCACIÓN SOBRE OTRAS CAJAS	  	  ############/
	//Colocar la caja sobre alguna de las ya contenidas en el contenedor
	cont=0;
	salir=false;
	it_col=colocadas.rbegin();
	for(i=0; i<=this->ultima_caja; ++i,++it_col)
	{
		cont++;

		if(it_col->puedeSoportarA(b,height))
		{
			
			//Recorro todas las cajas colocadas	
			int num_encima=0;
			colocadas_aux.clear();
			for(it_aux=colocadas.begin(); it_aux!=colocadas.end(); ++it_aux)
			{	
				//Si hay alguna que tenga por base la misma que la caja i, se inserta en el vector
				if(it_aux->getBase() == &(*it_col))
				{
					//Hay que comprobar si la que se inserta es igual de grande que la base, 
					//lo cual indica que ya no es posible colocar nada más encima de esa base y hay que salir de esa iteración 
					if(it_aux->getWidth()==it_col->getWidth()&&it_aux->getDepth()==it_col->getDepth())
					{
						salir = true;
						break;
					}
					else
					{
						colocadas_aux.push_back(*it_aux);
						++num_encima;
					}
				}
			}

			if(salir)
			{
				salir = false;			
				continue;
			}

			//Si no hay ninguna caja más directamente se coloca en la esquina con menor x y z de la base
			if(num_encima==0)
			{
				//Las esquinas coincidirán con la caja base 
				this->oz = it_col->getZ();
				this->ox = it_col->getX();
				//La coordenada Y sumará a la base la altura
				this->oy = it_col->getY() + it_col->getHeight();

			}
			else
			{
				//Coordenadas iniciales para desplazar la caja
				z_base=it_col->getZ();
				this->oz= z_base + it_col->getDepth();
				y_base =it_col->getY();
				this->oy = y_base + it_col->getHeight();
				x_base=it_col->getX();
				this->ox= x_base + it_col->getWidth()-b.getWidth();

				//La vamos metiendo hasta que choque con otra caja de las colocadas
				while(noChocaZ(this->ox,this->oy,this->oz,&b,colocadas_aux, num_encima)
					&& this->oz>z_base)
					--(this->oz);

				//La movemos hacia la izq. hasta chocar con otra de las colocadas
				while(noChocaX(this->ox,this->oy,this->oz,&b,colocadas_aux, num_encima)
					&& this->ox>x_base)
					--(this->ox);

				//Lo intentamos mover de nuevo hacia el origen tanto en Z como en X
				while(noChocaZ(this->ox,this->oy,this->oz,&b,colocadas_aux, num_encima)
					&& this->oz>z_base)
					--(this->oz);

				while(noChocaX(this->ox,this->oy,this->oz,&b,colocadas_aux, num_encima)
					&& this->ox>x_base)
					--(this->ox);
			}

			//Comprobar si la caja queda contenida dentro de la caja base
			if( this->oz+b.getDepth() > it_col->getZ()+it_col->getDepth()
				|| this->ox+b.getWidth() > it_col->getX()+it_col->getWidth())
				continue;

			//La caja cabe en la base del contenedor
			b.setBase(&(*it_col));
			it_col->setEsBase(true);

			//Se actualiza el peso soportado por la caja inferior
			it_col->addWeight(b.getWeight());

			//Guardamos las posiciones definitivas de la caja
			b.setX(this->ox); 	
			b.setZ(this->oz);
			b.setY(this->oy);

			//La caja entra sobre la otra: actualizo los datos del contenedor 
			it_col_base=it_col.base();
			this->insertBox(b,it_col_base);

			return true;
		}
	}

	//############	COLOCACIÓN SOBRE LA BASE DEL CONTENEDOR	  ############/
	//Si no se pudo colocar sobre otra, intentarlo en la base tras la última
	if(i>ultima_caja)
	{
		//Seleccionar una esquina para la caja
		if(selectBaseCorner(b,seleccionada))
		{
			//Comprobar que la no se supera el límite de peso del contenedor
			if(this->weight+b.getWeight() <= this->weight_lim)
			{
				//Actualizar la base de la caja
				b.setBase(NULL);
				
				//Darle coordenadas a la caja
				b.setX(seleccionada.x);
				b.setY(seleccionada.y);
				b.setZ(seleccionada.z);

				//Borrar las contenedor.esquinas que ocupa (debe existir)
				if(!delCorner(b,this->esquinas))
				{
					cerr<<"\n** No se encuentra la esquina para caja de allocateBox en esquinas";
				}
				
				//Podrían haber huecos a la izquierda, reajustamos la posición
				this->ox = seleccionada.x;
				this->oy = seleccionada.y;
				this->oz = seleccionada.z;

				while(noChocaX_2D(this->ox,this->oz,&b, cajasBase, cajasBase.size())
					&& this->ox>0)
					--(this->ox);

				while(noChocaZ_2D(this->ox,this->oz,&b, cajasBase, cajasBase.size())
					&& this->oz>0)
					--(this->oz);

				while(noChocaX_2D(this->ox,this->oz,&b, cajasBase, cajasBase.size())
					&& this->ox>0)
					--(this->ox);

				//Darle las nuevas coordenadas a la caja
				b.setX(this->ox);
				b.setY(this->oy);
				b.setZ(this->oz);
				
				//Generar las nuevas esquinas (guardarlas en contenedor.esquinas y en corner_list)
				this->genBaseCorners(b);

				//La caja entra en el contenedor: actualizo los datos del contenedor 
				this->pushBackBox(b);
				cleanCorners(*this,b);
				
				return true;
			}
		}
	}

	return false;
}


/*
*	Una vez sabemos las coordenadas de la caja, asigna el peso correspondiente a cada bloque
*
*/
void Container::asignaPesoBloque(Box &caja)
{

	LONGD vol_n=0.0;
	LONGD vol_v=0.0;
	double peso_n, peso_v,d_n;

	//BLOQUES A LO LARGO (Z)
	//Buscar el bloque al que pertenece la caja en función del oz actual
	for(int i=0; i<NUM_BLOQUES; i++)
	{	
		//Si caja.getZ() es mayor que bloque.ini y  caja.getZ()+caja.getDepth() es menor q bloque.fin, la caja esta completamente dentro
		if(caja.getZ()>=this->bloques_z[i].ini && caja.getZ()+caja.getDepth()<=this->bloques_z[i].fin)
		{
			this->bloques_z[i].weight+=caja.getWeight();
			break;
		}
		//Si si empieza entre ini y fin, y se pasa de fin
		//La caja se sale del bloque, calcular la proporcion correspondiente a cada bloque.
		else
			if(i+1<NUM_BLOQUES 
				 && caja.getZ() >=this->bloques_z[i].ini && caja.getZ() < this->bloques_z[i].fin
				 && caja.getZ()+caja.getDepth()>=this->bloques_z[i+1].ini)
			{
				//Desde caja.getZ() hasta bloque.fin 
				//Calculo el volumen proporcional de la caja
				d_n =(this->bloques_z[i].fin - caja.getZ());
				peso_v = (double)caja.getWeight();
				peso_n = d_n * peso_v / (double)caja.getDepth();
				this->bloques_z[i].weight+= peso_n;

				//Desde bloque.fin hasta caja.getZ()+caja.getDepth() sumarlo al bloque siguiente
				//Calculo el volumen proporcional de la caja
				peso_n = peso_v - peso_n;
				this->bloques_z[i+1].weight+= peso_n;
				break;
			}
		//Si caja.getZ() es mayor que bloque fin, pasar al siguiente bloque
	}

	vol_n=vol_v=0.0;
	//BLOQUES A LO ANCHO (X)
	//Buscar el bloque al que pertenece la caja en función del oz actual
	for(int i=0; i<NUM_BLOQUES; i++)
	{	
		//Si caja.getX() es mayor que bloque.ini y  caja.getZ()+caja.getWidth() es menor q bloque.fin, la caja esta completamente dentro
		if(caja.getX()>=this->bloques_x[i].ini && caja.getX()+caja.getWidth()<=this->bloques_x[i].fin)
		{
			this->bloques_x[i].weight+=caja.getWeight();
			break;
		}
		//Si si empiexa entre ini y fin, y se pasa de fin
		//La caja se sale del bloque, calcular la proporcion correspondiente a cada bloque.
		else
			if( (i+1) < NUM_BLOQUES &&
				caja.getX() >=this->bloques_x[i].ini && caja.getX() < this->bloques_x[i].fin
				 && caja.getX()+caja.getWidth()>=this->bloques_x[i+1].ini)
			{
				//Desde caja.getX() hasta bloque.fin 
				//Calculo el volumen proporcional de la caja
				d_n =(this->bloques_x[i].fin - caja.getX());
				peso_v = (double)caja.getWeight();
				peso_n = d_n * peso_v / (double)caja.getWidth();
				this->bloques_x[i].weight+= peso_n;

				//Desde bloque.fin hasta caja.getX()+caja.getWidth() sumarlo al bloque siguiente
				//Calculo el volumen proporcional de la caja
				peso_n = peso_v - peso_n;
				this->bloques_x[i+1].weight+= peso_n;
				break;
			}
		//Si caja.getX() es mayor que bloque fin, pasar al siguiente bloque
	}
}
Container& Container::operator=(const Container &c)
{
	this->height=c.height;
	this->width=c.width;
	this->depth=c.depth;
	this->weight=c.weight;
	this->height_lim=c.height_lim;
	this->weight_lim=c.weight_lim;
	this->volume=c.volume;
	this->colocadas= c.colocadas;
	this->bloques_z=c.bloques_z;
	this->bloques_x=c.bloques_x;
	this->ultima_caja=c.ultima_caja;	
	this->z_max=c.z_max;
    this->area=c.area;
	this->ocupacion=c.ocupacion;
	this->id = c.id;
	this->oz = c.oz;
	this->ox = c.ox;
	this->fitness_solucion = c.fitness_solucion;
	this->cliente = c.cliente;
	this->esquinas = c.esquinas;

	return *this;
}

void Container::asignaCajas(vector<pair<int,Box> > &cajas, int contenedor)
{	
	
	this->ultima_caja=-1;
	int i=0;  
	while( i<cajas.size())
	{
		if(cajas[i].first == contenedor)
		{
			this->pushBackBox(cajas[i].second);
		}
		i++;
	}
	
	
}

void checkCorner(corner &, Container &);
void joinCorners(list<corner>&, corner);
void Container::genBaseCorners(Box &caja)
{
	corner una_esquina;

	//**** Esquina en X ****//
	una_esquina.x=caja.getX()+caja.getWidth();
	una_esquina.y=caja.getY();
	una_esquina.z=caja.getZ();
	if(caja.getBase()!=NULL)
	{
		una_esquina.w=(caja.getBase()->getX()+caja.getBase()->getWidth())-(caja.getX()+caja.getWidth());
		una_esquina.h=this->height-(caja.getBase()->getY()+caja.getBase()->getHeight());
		una_esquina.d=(caja.getBase()->getZ()+caja.getBase()->getDepth())-caja.getZ();
	}
	else
	{
		una_esquina.w=this->width-una_esquina.x;
		una_esquina.h=this->height;
		una_esquina.d=this->depth-una_esquina.z;
	}
	una_esquina.base=caja.getBase();

	//Si alguna dimension no tiene tamaño, se descarta la esquina
	if(una_esquina.w > 0 && una_esquina.h > 0 && una_esquina.d > 0)
	{
		//Comprobar si se solapa con alguna caja de las ya colocadas
		//(puede retornar con dimensiones iguales a 0)
		checkCorner(una_esquina, *this);
		if(una_esquina.w > 0 && una_esquina.h > 0 && una_esquina.d > 0)
			joinCorners(this->esquinas, una_esquina);
	}

	//**** Esquina en Z ****//
	una_esquina.x=caja.getX();
	una_esquina.y=caja.getY();
	una_esquina.z=caja.getZ()+caja.getDepth();
	if(caja.getBase()!=NULL)
	{
		una_esquina.w=caja.getBase()->getX()+caja.getBase()->getWidth()-caja.getX();
		una_esquina.h=this->height-(caja.getBase()->getY()+caja.getBase()->getHeight());
		una_esquina.d=caja.getBase()->getZ()+caja.getBase()->getDepth()-(caja.getZ()+caja.getDepth());
	}
	else
	{
		una_esquina.w=this->width-una_esquina.x;
		una_esquina.h=this->height;
		una_esquina.d=this->depth-una_esquina.z;
	}
	una_esquina.base=caja.getBase();

	//Si alguna dimension no tiene tamaño, se descarta la esquina
	if(una_esquina.w > 0 && una_esquina.h > 0 && una_esquina.d > 0)
	{
		//Comprobar si se solapa con alguna caja de las ya colocadas
		//(puede retornar con dimensiones iguales a 0)
		checkCorner(una_esquina,*this);
		if(una_esquina.w > 0 && una_esquina.h > 0 && una_esquina.d > 0)
			joinCorners(this->esquinas, una_esquina);
	}
}

bool fitsCorner(Box &, corner &);
bool orderCorner(corner &c1, corner &c2)
{
	if(c1.w*c1.d > c2.w*c2.d)
		return true;
	else
		return false;
}
bool Container::selectBaseCorner(Box &caja, corner &es)
{
	list<corner> seleccionadas;
	list<corner>::iterator it_lista,
						   it_z;

	list<list<corner>::iterator> z, x;
	list<list<corner>::iterator>::iterator it_x;

	int z_min,
		x_min;

	bool ok;

	//Seleccionar las esquinas de la lista recibida (restringida al bloque seleccionado)
	//	que pueden soportar a la caja
	//Para soportar a la caja se debe cumplir:
	//		#La base de la esquina debe soportar la caja
	//		#Colocada la caja en esa esquina, aquélla no debe sobresalir de la base ni superar la altura del contenedor
	for(it_lista=this->esquinas.begin(); it_lista!=this->esquinas.end(); it_lista++)
	{
		//Eliminar las esquinas que no pueden contener nada
		/*if(it_lista->w<=0 || it_lista->h<=0 || it_lista->d<=0)
		{
			it_lista=esquinas.erase(it_lista);
			continue;
		}*/

		if(fitsCorner(caja,*it_lista) )
		{
			seleccionadas.push_back(*it_lista);
		}
	}
	
	//z_min=x_min=INT_MAX;
	ok=false;
	//it_z=seleccionadas.begin();

	seleccionadas.sort(orderCorner);

	//Buscar las esquinas de Z mínimo
	/*while(it_z!=seleccionadas.end())
	{
		if((*it_z).z<=z_min)
		{
			if((*it_z).z<z_min)
			{
				z.clear();
			}

			z_min=(*it_z).z;
			z.push_back(it_z);
		}
		++it_z;
	}
	
	//De entre las de Z, la de X mínimo
	it_x=z.begin();
	for(;it_x!=z.end(); ++it_x)
	{
		if((*(*it_x)).x<=x_min)
		{
			if((*(*it_x)).x<x_min)
			{
				x.clear();
			}

			x_min=(*(*it_x)).x;
			x.push_back(*it_x);
			ok=true;
		}
	}

	if(ok)
	{
		es=*(x.front());
	}*/

	if(seleccionadas.size()>0)		//SÓLO PARA THPACK
	{
		es=seleccionadas.front();
		ok=true;
	}

	return ok;
}


#endif

